package com.waming.spring.boot.elasticsearch.service;

import com.waming.spring.boot.elasticsearch.config.TrainConstants;
import com.waming.spring.boot.elasticsearch.entity.TrainStation;
import com.waming.spring.boot.elasticsearch.entity.TrainStationV1;
import com.waming.spring.boot.elasticsearch.entity.TrainStationV2;
import com.waming.spring.boot.elasticsearch.mapper.TrainStationV1Repository;
import com.waming.spring.boot.elasticsearch.mapper.TrainStationV2Repository;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.lucene.search.function.FunctionScoreQuery;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.AliasQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service("trainStationService")
public class TrainStationServiceImpl implements ITrainStationService {
    private static Logger logger= LogManager.getLogger(TrainStationServiceImpl.class);
    private TrainStationV1Repository trainStationV1Repository;
    private TrainStationV2Repository trainStationV2Repository;
    private ElasticsearchOperations elasticsearchOperations;

    @Override
    public boolean createAlias(String aliasName, String indexName) {
        boolean rs=true;
        IndexOperations indexOperations=elasticsearchOperations.indexOps(IndexCoordinates.of(indexName));
        if(indexOperations.exists()){
            rs=indexOperations.addAlias(new AliasQuery(aliasName));
        }
        return rs;
    }

    @Override
    public boolean deleteAlias(String aliasName, String indexName) {
        boolean rs=true;
        IndexOperations indexOperations=elasticsearchOperations.indexOps(IndexCoordinates.of(indexName));
        if(indexOperations.exists()){
            rs=indexOperations.removeAlias(new AliasQuery(aliasName));
        }
        return rs;
    }

    public List<TrainStation> searchUsersByKeywordWithAlias(String keyword,String indexName) {
        Pageable pageable = PageRequest.of(0, 15);
        List<TrainStation> stations=new ArrayList<>(15);
        if(TrainConstants.TRAIN_STATION_V1.equals(indexName)){
            Page<TrainStationV1> page1=trainStationV1Repository.searchByNameAndPinyinAndNameEn(keyword,pageable);
            stations.addAll(page1.toList());
        }else{
            Page<TrainStationV2> page2=trainStationV2Repository.searchByNameAndPinyinAndNameEn(keyword,pageable);
            stations.addAll(page2.toList());
        }
        return stations;
    }

    public List<TrainStation> searchByKeyword(String keyword,String indexName) {
        // Build the multi-match query
        MultiMatchQueryBuilder multiMatchQuery = QueryBuilders.multiMatchQuery(keyword, "name", "pinyin", "cityPinyin").operator(Operator.AND);
        // Define weight factor functions
        List<FunctionScoreQueryBuilder.FilterFunctionBuilder> filterFunctions = new ArrayList<>();
        filterFunctions.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                QueryBuilders.matchQuery("name", keyword).operator(Operator.AND),
                ScoreFunctionBuilders.weightFactorFunction(1.0F)));
        filterFunctions.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                QueryBuilders.matchQuery("pinyin", keyword).operator(Operator.AND),
                ScoreFunctionBuilders.weightFactorFunction(0.9F)));
        filterFunctions.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                QueryBuilders.matchQuery("simplyPinyin", keyword).operator(Operator.AND),
                ScoreFunctionBuilders.weightFactorFunction(0.8F)));

        filterFunctions.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                QueryBuilders.matchQuery("cityName", keyword).operator(Operator.AND),
                ScoreFunctionBuilders.weightFactorFunction(0.8F)));
        filterFunctions.add(new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                QueryBuilders.matchQuery("cityPinyin", keyword).operator(Operator.AND),
                ScoreFunctionBuilders.weightFactorFunction(0.7F)));

        FunctionScoreQueryBuilder.FilterFunctionBuilder[] functionBuilders= filterFunctions.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]);
        // Build the function score query
        FunctionScoreQueryBuilder functionScoreQuery = QueryBuilders.functionScoreQuery(multiMatchQuery,functionBuilders
                ).scoreMode(FunctionScoreQuery.ScoreMode.SUM);
        PageRequest page = PageRequest.of(0, 15);
        // Build the native search query
        QueryBuilder statusFilter = QueryBuilders.termQuery("status",1);

        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withQuery(functionScoreQuery)
                .withPageable(page)
                .withFilter(statusFilter)
                .build();
        String queryDSL = nativeSearchQuery.getQuery().toString();
        if(logger.isDebugEnabled()){
            logger.debug(queryDSL);
        }
        //支持别名查询
        SearchHits<TrainStation> searchHits = elasticsearchOperations.search(nativeSearchQuery, TrainStation.class, IndexCoordinates.of(indexName));
        return searchHits.map(searchHit -> searchHit.getContent()).toList();
    }

    @Override
    public Map<String,TrainStation> searchDefault(List<String> cityCodes,String indexName) {
        // Build the BoolQueryBuilder
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        QueryBuilder qc = QueryBuilders.termsQuery("cityCode", cityCodes);
        QueryBuilder qb = QueryBuilders.termQuery("defaultStation",1);
        boolQuery.must(qc);
        boolQuery.must(qb);
        PageRequest page = PageRequest.of(0, 15);
        NativeSearchQuery nativeSearchQuery = new NativeSearchQueryBuilder()
                .withFilter(boolQuery)
                .withPageable(page)
                .build();
        String queryDSL = nativeSearchQuery.getFilter().toString();
        if(logger.isDebugEnabled()){
            logger.debug(queryDSL);
        }
        // Execute the query
        SearchHits<TrainStation> searchHits = elasticsearchOperations.search(nativeSearchQuery, TrainStation.class,IndexCoordinates.of(indexName));
        return searchHits.stream().map(searchHit -> searchHit.getContent()).collect(Collectors.toMap(TrainStation::getCityCode,trainStation -> trainStation,(existingValue, newValue) -> {
            return existingValue;
        }));
    }


    @Override
    public Boolean batchSave(List<TrainStation> trainStations) {
        if(trainStations==null || trainStations.isEmpty()){
            return true;
        }
        TrainStation trainStation=trainStations.get(0);
        if(trainStation instanceof TrainStationV1){
            List<TrainStationV1> stationV1s = trainStations.stream()
                    .map(station -> {
                        TrainStationV1 v1 = new TrainStationV1();
                        BeanUtils.copyProperties(station,v1);
                        // 执行属性拷贝或其他逻辑，将 station 转换为 v1
                        return v1;
                    }).collect(Collectors.toList());
            trainStationV1Repository.saveAll(stationV1s);
        } else if (trainStation instanceof TrainStationV2){
            List<TrainStationV2> stationV2s = trainStations.stream()
                    .map(station -> {
                        TrainStationV2 v2 = new TrainStationV2();
                        // 执行属性拷贝或其他逻辑，将 station 转换为 v2
                        BeanUtils.copyProperties(station,v2);
                        return v2;
                    }).collect(Collectors.toList());
            trainStationV2Repository.saveAll(stationV2s);
        }else{
            logger.warn("trainStation is not instance of TrainStationV1 or TrainStationV2");
        }
        return true;
    }

    @Override
    public Boolean update(TrainStation trainStation) {
        if(trainStation==null){
            return true;
        }
        if(trainStation instanceof TrainStationV1){
            trainStationV1Repository.save((TrainStationV1) trainStation);
        } else if (trainStation instanceof TrainStationV2){
            trainStationV2Repository.save((TrainStationV2) trainStation);
        }else{
            logger.warn("trainStation is not instance of TrainStationV1 or TrainStationV2");
        }
        return true;
    }

    @Override
    public Boolean delete(TrainStation trainStation) {
        if(trainStation==null){
            return true;
        }
        if(trainStation instanceof TrainStationV1){
            trainStationV1Repository.delete((TrainStationV1) trainStation);
        } else if (trainStation instanceof TrainStationV2){
            trainStationV2Repository.delete((TrainStationV2) trainStation);
        }else{
            logger.warn("trainStation is not instance of TrainStationV1 or TrainStationV2");
        }
        return true;
    }

    @Autowired
    public TrainStationServiceImpl setTrainStationV1Repository(TrainStationV1Repository trainStationV1Repository) {
        this.trainStationV1Repository = trainStationV1Repository;
        return this;
    }

    @Autowired
    public TrainStationServiceImpl setTrainStationV2Repository(TrainStationV2Repository trainStationV2Repository) {
        this.trainStationV2Repository = trainStationV2Repository;
        return this;
    }

    @Autowired
    public void setElasticsearchOperations(ElasticsearchOperations elasticsearchOperations) {
        this.elasticsearchOperations = elasticsearchOperations;
    }
}
